import threading

from Bases import OSCBaseObject

def make_css(color_list):
    if type(color_list) == str:
        return color_list
    return '#%s' % (''.join(['%02x' % (i) for i in color_list]))

class BaseWidget(OSCBaseObject):
    _widget_type = None
    _interface_properties = ['name', 'type', 'min', 'max', 'oninit']
    _attribute_defaults = {'min':0, 'max':1}
    def __init__(self, **kwargs):
        self.interface_properties = set()
        cls = self.__class__
        while cls != BaseWidget.__bases__[0]:
            if hasattr(cls, '_interface_properties'):
                self.interface_properties |= set(cls._interface_properties)
            cls = cls.__bases__[0]
        self.link_state = False
        self.name = kwargs.get('name')
        #self.id = kwargs.get('id', self.name)
        kwargs.setdefault('osc_address', self.name)
        super(BaseWidget, self).__init__(**kwargs)
        self.id = kwargs.get('id', '_'.join(self.osc_node.get_full_path()[2:]))
        self.register_signal('interface_update', 'add_widget', 'remove_widget')
        self.use_int_value = kwargs.get('use_int_value', False)
        self.osc_root_address = kwargs.get('osc_root_address', 'iOSCControl')
        self.osc_client_address = kwargs.get('osc_client_address')
        self.type = self.__class__._widget_type
        self.min = kwargs.get('min')
        self.max = kwargs.get('max')
        for key in ['oninit', 'onvaluechange']:
            if key in kwargs:
                setattr(self, key, kwargs[key])
        
        cls = self.__class__
        while cls != OSCBaseObject:
            defaults = getattr(cls, '_attribute_defaults', None)
            if defaults:
                for key, val in defaults.iteritems():
                    if not hasattr(self, key) and kwargs.get(key) != val:
                        setattr(self, key, kwargs.get(key))
            cls = cls.__bases__[0]
        
        self.add_osc_handler(callbacks={'set-value':self.on_osc_set_value})
        
        self.child_widgets = {}
        
        self.src_signal = None
        if self.osc_node.oscMaster:
            self.set_link_state(True)
        self.set_src_object(**kwargs)
            
    def unlink(self):
        self.set_link_state(False)
        super(BaseWidget, self).unlink()
        
    def set_link_state(self, state):
        if state != self.link_state:
            if state:
                self.add_osc_handler(callbacks={'set-value':self.on_osc_set_value})
                if self.src_signal:
                    self.src_object.bind(**{self.src_signal:self.on_src_object_update})
            else:
                self.remove_osc_handler(id=self.osc_address)
                if self.src_signal:
                    self.src_object.unbind(self.on_src_object_update)
        self.link_state = state
    
    def set_src_object(self, **kwargs):
        if self.link_state and self.src_signal:
            self.src_object.unbind(self.on_src_object_update)
        self.src_object = kwargs.get('src_object')
        self.src_attr = kwargs.get('src_attr')
        self.src_signal = kwargs.get('src_signal')
        if self.link_state and self.src_signal:
            min = getattr(self.src_object, 'value_min', None)
            max = getattr(self.src_object, 'value_max', None)
            if min is not None and max is not None:
                if min != self.min or max != self.max:
                    self.min = min
                    self.max = max
                    self._interface_update('setRange', [self.min, self.max])
            self.on_src_object_update()
            id = self.src_object.bind(**{self.src_signal:self.on_src_object_update})
            print 'widget connect: ', self.name, id, self.src_object
        
    def add_widget(self, cls, **kwargs):
        for key in ['osc_root_address', 'osc_client_address']:
            kwargs.setdefault(key, getattr(self, key))
        kwargs.setdefault('osc_parent_node', self.osc_node)
        widget = cls(**kwargs)
        self.child_widgets.update({widget.name:widget})
        widget.connect('interface_update', self.on_child_interface_update)
        widget.bind(interface_update=self.on_child_interface_update, 
                    add_widget=self.on_child_add_widget, 
                    remove_widget=self.on_child_remove_widget)
        self.emit('add_widget', parent=self, widget=widget)
        return widget
        
    def on_child_add_widget(self, **kwargs):
        self.emit('add_widget', **kwargs)
        
    def remove(self):
        for w in self.child_widgets.itervalues():
            w.remove()
        self.unlink()
        self._interface_update('removeWidget', [])
        
    def refresh_interface(self):
        for w in self.child_widgets.itervalues():
            w.refresh_interface()
        
    def on_child_remove_widget(self, **kwargs):
        self.emit('remove_widget', **kwargs)
        
    def send_value_to_osc(self, value, address='set-value'):
        if self.osc_client_address is None:
            self.osc_client_address = 'DWT_iPad'
        self.osc_node.send_message(root_address=self.osc_client_address, address=address, value=value)
    
    def on_osc_set_value(self, **kwargs):
        pass
        
    def on_src_object_update(self, **kwargs):
        pass
        
    def _interface_update(self, update_type, values, add_widget_id=True):
        sig_kwargs = dict(address=update_type, value=values)
        if add_widget_id:
            sig_kwargs['widget'] = self
        self.emit('interface_update', **sig_kwargs)
        
    def on_child_interface_update(self, **kwargs):
        self.emit('interface_update', **kwargs)
        
    def build_interface_dict(self, **kwargs):
        root = kwargs.get('root_address')
        d = {}
        for key in self.interface_properties:
            val = getattr(self, key, None)
            if key == 'name':
                val = self.id
            if hasattr(self, key) and type(val) != set:
                d.update({key:val})
        if self._widget_type in ['Label']:
            for key in ['min', 'max']:
                if key in d:
                    del d[key]
            
        path = self.osc_node.get_full_path()
        if root:
            path[0] = root
        path[1] = self.osc_root_address
        s = '/' + '/'.join(path) + '/set-value'
        d.update({'address':s})
        if 'osc_address' in d:
            del d['osc_address']
        l = [d]
        for w in self.child_widgets.itervalues():
            wl = w.build_interface_dict(**kwargs)
            if wl:
                l += wl
        return l
        
class GraphicsWidget(BaseWidget):
    _interface_properties = ['bounds', 'colors', 'ontouchstart', 'ontouchmove', 'ontouchend', 'requiresTouchDown']#, 'backgroundColor', 'foregroundColor', 'strokeColor']
    _color_keys = ['backgroundColor', 'foregroundColor', 'strokeColor']
    def __init__(self, **kwargs):
        super(GraphicsWidget, self).__init__(**kwargs)
        if 'bounds' in kwargs:
            self.bounds = kwargs.get('bounds')
        else:
            self.x = kwargs.get('x', 0)
            self.y = kwargs.get('y', 0)
            self.width = kwargs.get('width', .25)
            self.height = kwargs.get('height', .25)
        self.backgroundColor = kwargs.get('backgroundColor', [32]*4)
        self.foregroundColor = kwargs.get('foregroundColor', [128]*3)
        self.strokeColor = kwargs.get('strokeColor', [255]*3)
        for s in ['start', 'move', 'end']:
            key = 'ontouch' + s
            if key in kwargs:
                setattr(self, key, kwargs[key])
#        if not isinstance(self, Label):
#            self.label_widget = self.add_widget(Label, name='label', 
#                                                       bounds=self.bounds, 
#                                                       foregroundColor=[255]*3, 
#                                                       value=kwargs.get('label', ''))
#                                                       #osc_parent_node=self.osc_node, 
#                                                       #osc_address='label')
#            
#            self.bind(label=self.on_label_set)
#            #if 'label' in kwargs:
#            #    self.label = kwargs.get('label')
            
    @property
    def backgroundColor(self):
        return make_css(self._backgroundColor)
    @backgroundColor.setter
    def backgroundColor(self, value):
        self._backgroundColor = value
    @property
    def foregroundColor(self):
        return make_css(self._foregroundColor)
    @foregroundColor.setter
    def foregroundColor(self, value):
        self._foregroundColor = value
    @property
    def strokeColor(self):
        return make_css(self._strokeColor)
    @strokeColor.setter
    def strokeColor(self, value):
        self._strokeColor = value
    @property
    def bounds(self):
        return [getattr(self, key) for key in ['x', 'y', 'width', 'height']]
    @bounds.setter
    def bounds(self, value):
        for x, key in enumerate(['x', 'y', 'width', 'height']):
            setattr(self, key, value[x])
    @property
    def colors(self):
        return [getattr(self, key) for key in self._color_keys]
    @colors.setter
    def colors(self, value):
        if len(value) == len(self._color_keys):
            for key, val in zip(self._color_keys, value):
                setattr(self, key, val)
            
        
class Button(GraphicsWidget):
    _widget_type = 'Button'
    _interface_properties = ['mode', 'startingValue', 'label']
    _Properties = {'label':dict(default='')}
    #_attribute_defaults = {'mode':'toggle'}
    def __init__(self, **kwargs):
        self._touch_state = False
        #kwargs.setdefault('width', .1)
        #kwargs.setdefault('height', .1)
        kwargs.update({'min':-1., 'max':1.})#, 'requiresTouchDown':False})
        self.color_keys = ['backgroundColor', 'foregroundColor', 'strokeColor']
        self.state_colors = {False:[[64, 64, 64, 8], [64]*3, [190]*3], 
                             True: [[32, 32, 64, 64], [100, 100, 140], [255]*3]}
        for i, key in enumerate(self.color_keys):
            kwargs[key] = self.state_colors[False][i]
            
        self.mode = kwargs.get('mode', 'contact')
        self.startingValue = kwargs.get('min')
        super(Button, self).__init__(**kwargs)
        self.register_signal('state_changed')
        self.label = kwargs.get('label', '')
        
        self.dummy_label_widget = self.add_widget(DummyButtonLabel, parent=self)
        
        self.bind(label=self.on_label_set)
        
    @property
    def touch_state(self):
        return self._touch_state
    @touch_state.setter
    def touch_state(self, value):
        if value != self.touch_state:
            self._touch_state = value
            self.update_state_color()
            #self.send_value_to_osc(self.max)
            
        #self._touch_state = value
        #if value:
        #    i = self.max
        #else:
        #    i = self.min
        #self.send_value_to_osc(i)
        
    def update_state_color(self):
        for i, key in enumerate(self.color_keys):
            setattr(self, key, self.state_colors[self.touch_state][i])
        colors = [getattr(self, key) for key in self.color_keys]
        self._interface_update('setColors', colors)
        
    def on_osc_set_value(self, **kwargs):
        self._touch_state = not self._touch_state
        self.update_state_color()
        self.emit('state_changed', widget=self, state=self.touch_state)
        #state = kwargs.get('values')[0] > self.min
        #if state != self.touch_state:
        #    self._touch_state = state
        #    self.emit('state_changed', widget=self, state=state)
        
    def on_label_set(self, **kwargs):
        value = kwargs.get('value')
        self.dummy_label_widget.value = value
        
    def refresh_interface(self):
        self.dummy_label_widget.refresh_interface()
        super(Button, self).refresh_interface()
        
    def remove(self):
        self.dummy_label_widget.remove()
        super(Button, self).remove()
        
        
class DummyButtonLabel(GraphicsWidget):
    _interface_properties = ['verticalCenter', 'align', 'value', 'labelSize']
    _Properties = {'value':dict(default='')}
    def __init__(self, **kwargs):
        self.parent_widget = kwargs.get('parent')
        kwargs.update({'osc_parent_node':self.parent_widget.osc_node, 
                       'name':self.parent_widget.name + 'Label', 
                       'id':self.parent_widget.id + 'Label'})
        super(DummyButtonLabel, self).__init__(**kwargs)
        self.interface_properties.discard('bounds')
        self.verticalCenter = kwargs.get('verticalCenter', True)
        self.align = kwargs.get('align', 'center')
        self.labelSize = kwargs.get('labelSize', 8)
        self.value = kwargs.get('value', self.parent_widget.label)
        self.backgroundColor = [0]*4
        self.foregroundColor = [255]*3
        self.strokeColor = [255]*3
        self.bind(value=self.on_value_set)
    
    def build_interface_dict(self, **kwargs):
        return False
        
    def on_value_set(self, **kwargs):
        value = kwargs.get('value')
        self._interface_update('runScript', "%s.setValue('%s')" % (self.id, value), add_widget_id=False)
        
    def refresh_interface(self):
        self._interface_update('setColors', self.colors)
        self._interface_update('runScript', "%s.setValue('%s')" % (self.id, self.value), add_widget_id=False)
        super(DummyButtonLabel, self).refresh_interface()
        #for key in self.interface_properties:
        #    self._interface_update('runScript', "%s.setValue('%s')" % (self.id, getattr(self, key)
        
class MultiButton(GraphicsWidget):
    _widget_type = 'MultiButton'
    _interface_properties = ['rows', 'columns', 'shouldLabel']
    def __init__(self, **kwargs):
        self.rows = kwargs.get('rows', 4)
        self.columns = kwargs.get('columns', 4)
        self.shouldLabel = True
        super(MultiButton, self).__init__(**kwargs)
        
class DummyButton(Button):
    def __init__(self, **kwargs):
        self.parent_widget = kwargs.get('parent')
        self.index = kwargs.get('index')
        
        
class Toggle(Button):
    #_widget_type = 'Button'
    def __init__(self, **kwargs):
        kwargs['mode'] = 'toggle'
        super(Toggle, self).__init__(**kwargs)

class Slider(GraphicsWidget):
    _widget_type = 'Slider'
    _interface_properties = ['isVertical', 'isXFader']
    _attribute_defaults = {'isVertical':False, 'isXFader':False}
    def __init__(self, **kwargs):
        self.throttle_timer = None
        self.throttle_wait = threading.Event()
        self.throttle_timeout = .1
        self.throttle_last_msg = None
        self._value = 0.
        self.update_label_with_value = kwargs.get('update_label_with_value', True)
        kwargs.setdefault('foregroundColor', [0, 255, 0])
        kwargs.setdefault('backgroundColor', [128, 128, 128, 255])
        kwargs.setdefault('min', 0.)
        kwargs.setdefault('max', 1.)
        super(Slider, self).__init__(**kwargs)
        #self.ontouchstart = 'window.slowSliderCount = 0; PhoneGap.exec(\'OSCManager.send\', this.address, \'f\', this.value)'
        #self.ontouchmove = 'window.slowSliderCount++; if(window.slowSliderCount == 10) { window.slowSliderCount = 0; PhoneGap.exec(\'OSCManager.send\', this.address, \'f\', this.value)'
        self.label_widget = None
        self.value_label = None
        if kwargs.get('add_label', True):
            self.add_name_label(**kwargs)
        if kwargs.get('add_value_label', True):
            self.add_value_label(**kwargs)
        self.register_signal('value_update')
    def add_name_label(self, **kwargs):
        bounds = self.bounds[:]
        h = .05
        bounds[1] = bounds[1] + bounds[3]
        bounds[3] = h
        self.label_widget = self.add_widget(Label, 
                                            name=self.name+'Label', 
                                            bounds=bounds, 
                                            value=kwargs.get('label', ''))
    def add_value_label(self, **kwargs):
        bounds = self.bounds[:]
        h = .05
        bounds[1] = bounds[1] + bounds[3] + h
        bounds[3] = h
        self.value_label = self.add_widget(Label, 
                                           name=self.name+'ValueLabel', 
                                           bounds=bounds, 
                                           value=str(self.value))
        
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, value):
        if self.use_int_value:
            value = int(value)
        if value != self.value:
            self._value = value
            self.send_value_to_osc(value)
            if self.update_label_with_value and self.value_label:
                self.value_label.value = '%.2f' % (value)
            self.emit('value_update', widget=self, value=value)
        
    @property
    def label(self):
        if not self.label_widget:
            return ''
        return self.label_widget.value
    @label.setter
    def label(self, value):
        if self.label_widget:
            self.label_widget.value = value
        
    def on_osc_set_value(self, **kwargs):
        #if self.throttle_wait.isSet():
        #    self.throttle_last_msg = kwargs.copy()
        #    return
        self.process_osc_msg(**kwargs)
        #self.throttle_wait.set()
        #self.throttle_timer = threading.Timer(self.throttle_timeout, self.on_throttle_timer)
        #self.throttle_timer.start()
        
    def on_throttle_timer(self):
        print self, 'timer end', self.throttle_last_msg
        if self.throttle_last_msg is not None:
            self.process_osc_msg(**self.throttle_last_msg)
        self.throttle_last_msg = None
        self.throttle_wait.clear()
            
    def process_osc_msg(self, **kwargs):
        value = kwargs.get('values')[0]
        if self.use_int_value:
            value = int(value)
        if value != self.value:
            self._value = value
            if self.src_object:
                setattr(self.src_object, self.src_attr, value)
            if self.update_label_with_value and self.value_label:
                self.value_label.value = '%.2f' % (value)
            self.emit('value_update', widget=self, value=value)
        
    def on_src_object_update(self, **kwargs):
        value = getattr(self.src_object, self.src_attr)
        if value != self.value:
            self.value = value
        
class VSlider(Slider):
    _widget_type = 'Slider'
    def __init__(self, **kwargs):
        self.isVertical = True
        super(VSlider, self).__init__(**kwargs)
        
class HSlider(Slider):
    _widget_type = 'Slider'
    def __init__(self, **kwargs):
        kwargs.update({'add_label':False, 'add_value_label':False})
        super(HSlider, self).__init__(**kwargs)
        
class MultiSlider(Slider):
    _widget_type = 'MultiSlider'
    _interface_properties = 'numberOfSliders'
    def __init__(self, **kwargs):
        kwargs.update(dict(zip(['add_label', 'add_value_label', 'update_label_with_value'], [False]*3)))
        super(MultiSlider, self).__init__(**kwargs)
        self.numberOfSliders = kwargs.get('numberOfSliders', 4)
    def set_child_colors(self, d):
        for key, val in d.iteritems():
            if type(key) == int:
                css = ', '.join([make_css(l).join(["'", "'"]) for l in val])
                s = "%s.children[%s].setColors([%s])" % (self.id, key, css)
                self._interface_update('runScript', s, add_widget_id=False)

class Label(GraphicsWidget):
    _widget_type = 'Label'
    _interface_properties = ['verticalCenter', 'align', 'value']
    def __init__(self, **kwargs):
        self._value = ''
        #self._backgroundColor = [0, 0, 0, 0]
        kwargs.setdefault('foregroundColor', [255]*3)
        super(Label, self).__init__(**kwargs)
        self.register_signal('value_update')
        self.verticalCenter = kwargs.get('verticalCenter', True)
        self.align = kwargs.get('align', 'center')
        self._value = kwargs.get('value', '')
        #self.strokeColor = [0, 255, 0]
        
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, value):
        if value != self.value:
            self._value = value
            self.send_value_to_osc(value)
            
    def on_osc_set_value(self, **kwargs):
        value = kwargs.get('values')[0]
        if value != self.value:
            self._value = value
            self.emit('value_update', widget=self, value=value)
        
    def on_src_object_update(self, **kwargs):
        value = getattr(self.src_object, self.src_attr)
        if value != self.value:
            self.value = value
            
class xyPad(GraphicsWidget):
    _widget_type = 'MultiTouchXY'
    _interface_properties = ['isMomentary', 'numberOfTouches']
    def __init__(self, **kwargs):
        self._pos = [0, 0]
        super(xyPad, self).__init__(**kwargs)
        self.register_signal('value_update')
        self.isMomentary = kwargs.get('isMomentary', True)
        self.xyInverted = kwargs.get('xyInverted', (False, True))
        self.numberOfTouches = 1
        #self.scale_factor = kwargs.get('scale_factor', 1)
        
    @property
    def pos(self):
        return self._pos
    @pos.setter
    def pos(self, value):
        if value != self.pos:
            #xy = [x / self.max for x in value]
            self._invert_xy(value)
            self.send_value_to_osc(value)
            
    def _invert_xy(self, xy):
        for i in range(2):
            if self.xyInverted[i]:
                xy[i] = (xy[i] * -1) + self.max + self.min
                
    def on_osc_set_value(self, **kwargs):
        xy = kwargs.get('values')
        self._invert_xy(xy)
        #print self.name, xy
        if xy[0] >= self.min and xy[1] >= self.min:# and xy != self.pos:
            self._pos = xy
            if self.src_object:
                setattr(self.src_object, self.src_attr, xy)
            self.emit('value_update', widget=self, value=self.pos)
            
    def on_src_object_update(self, **kwargs):
        value = getattr(self.src_object, self.src_attr)
        if value != self.pos:
            #print 'xy obj update:', value
            self.pos = value

w_list = [Button, Toggle, VSlider, HSlider, Label, xyPad]
widget_classes = dict(zip([cls.__name__ for cls in w_list], w_list))